﻿/********************************************************************************
 * Developed by Phil Tyler (http://www.laidbackcoder.co.uk)
 * 
 * Cryptosolic is Licensed under the GNU Lesser General Public License (LGPL)
 * http://cryptosolic.codeplex.com/license
 * *****************************************************************************/
using System;
using System.ComponentModel;
using System.IO;
using System.Security.Cryptography;
using Cryptosolic.Security.Encryption.Exceptions;
using Cryptosolic.Security.Hashing;

namespace Cryptosolic.Security.Encryption.Symmetric
{
	/// <summary>
	/// Wrapper for Symmetric Encryption Algrithms that are of type
	/// System.Security.Cryptography.SymmetricAlgorithm
	/// </summary>
    /// <remarks>
    /// Used only to provide Base Methods to Sub Classes - DO NOT instantiate directly
    /// </remarks>
	[Browsable(false)]
	[EditorBrowsable(EditorBrowsableState.Never)]
	public abstract class SscSymmetricAlgorithm : SymmetricEncryptionAlgorithm
	{
		#region Properties
		
			/// <summary>
	        /// Hashing Algorithm used to Generate Key
	        /// </summary>
			public abstract override IHashingAlgorithm HashingAlg { get; }
		
			/// <summary>
            /// Method to Pad Cipher Text
            /// </summary>
			public abstract override BytePaddingMode Padding { get; }
			
			/// <summary>
            /// Method used to Undertake Encryption/Decryption
            /// </summary>
			public abstract override BlockCipherMode Mode  { get; }
					
			/// <summary>
	        /// Key Size of Cipher (in bits)
	        /// </summary>
			public abstract override int KeySize { get; }
			
			/// <summary>
	        /// Bloc Size of Cipher (in bits)
	        /// </summary>
			public abstract override int BlockSize  { get; }
			
			/// <summary>
	       	/// Symmetric Encryption Algorithm used for Encryption and Decryption
	       	/// </summary>
			internal abstract SymmetricAlgorithm EncryptionAlg { get; }
				
		#endregion
						
		#region Helper Methods
		
			/// <summary>
			/// Converts Cryptosolic Paddind Mode to SSC Padding Mode
			/// </summary>
			/// <returns></returns>
			private PaddingMode GetPaddingMode()
			{
				switch(Padding)
				{
					case BytePaddingMode.ANSIX923:
						return PaddingMode.ANSIX923;
						
					case BytePaddingMode.ISO10126:
						return PaddingMode.ISO10126;
						
					case BytePaddingMode.None:
						return PaddingMode.None;
						
					case BytePaddingMode.PKCS7:
						return PaddingMode.PKCS7;
						
					case BytePaddingMode.Zeros:
						return PaddingMode.Zeros;
						
					default:
						throw new UnsupportedPaddingModeException("Please Specify a Supported Padding Mode");
						
				}
			}
			
			/// <summary>
			/// Converts Cryptosolic Padding Mode to SSC Padding Mode
			/// </summary>
			/// <returns></returns>
			private CipherMode GetCipherMode()
			{
				switch(Mode)
				{
					case BlockCipherMode.CBC:
						return CipherMode.CBC;
						
					case BlockCipherMode.CFB:
						return CipherMode.CFB;
						
					case BlockCipherMode.CTS:
						return CipherMode.CTS;
						
					case BlockCipherMode.ECB:
						return CipherMode.ECB;
						
					case BlockCipherMode.OFB:
						return CipherMode.OFB;
						
					default:
						throw new UnsupportedCipherModeException("Please Specify a Supported Cipher Mode");
				}
			}
		
		#endregion
	
		/// <summary>
        /// Encrypt the Human Readable Data
        /// </summary>
        /// <param name="data">Human Readable Data to be Encrypted</param>
        /// <param name="symmetricKey">Symetric Encryption/Decryption Key</param>
        /// <param name="salt">Salt</param>
        /// <param name="iV">Initialisation Vector</param>
        /// <returns>Encrypted Data</returns>
        public override CipherText EncryptData(byte[] data, string symmetricKey, string salt, byte[] iV)
        {
        	MemoryStream memoryStream = null;
            CryptoStream cryptoStream = null;
 
            try
            {
            	byte[] key = this.ComputeKey(symmetricKey, salt);
            	
            	EncryptionAlg.KeySize = KeySize;
            	EncryptionAlg.BlockSize = BlockSize;
            	EncryptionAlg.Mode = this.GetCipherMode();
                EncryptionAlg.Padding = this.GetPaddingMode();
            	EncryptionAlg.Key = key;
                EncryptionAlg.IV = iV;

                memoryStream = new MemoryStream();
                cryptoStream = new CryptoStream(memoryStream, EncryptionAlg.CreateEncryptor(), CryptoStreamMode.Write);
                cryptoStream.Write(data, 0, data.Length);
                cryptoStream.FlushFinalBlock();
                cryptoStream.Close();
            }
            catch(Exception e)
            {
            	throw new EncryptionFailureException(e.Message);
            }
            finally
            {
            	if(EncryptionAlg != null)
            	{
            		EncryptionAlg.Clear();
            	}
            }
            
            return new CipherText(memoryStream.ToArray());
        	
        }
        
        /// <summary>
        /// Decrypt Encrypted Data
        /// </summary>
        /// <param name="data">Encrypted Data to be Decrypted</param>
        /// <param name="symmetricKey">Symetric Encryption/Decryption Key</param>
        /// <param name="salt">Salt</param>
        /// <param name="iV">Initialisation Vector</param>
        /// <returns>Human Readable Data</returns>
        /// <remarks>
        /// This method will throw an Exception if not Overridden!
        /// </remarks>
        public override PlainText DecryptData(byte[] data, string symmetricKey, string salt, byte[] iV)
        {
        	MemoryStream memoryStream = null;
            CryptoStream cryptoStream = null;
 
            try
            {
            	byte[] key = this.ComputeKey(symmetricKey, salt);
            	
            	EncryptionAlg.KeySize = KeySize;
            	EncryptionAlg.BlockSize = BlockSize;
            	EncryptionAlg.Mode = this.GetCipherMode();
            	EncryptionAlg.Padding = this.GetPaddingMode();
            	EncryptionAlg.Key = key;
                EncryptionAlg.IV = iV;
                
                memoryStream = new MemoryStream();
                cryptoStream = new CryptoStream(memoryStream, EncryptionAlg.CreateDecryptor(), CryptoStreamMode.Write);
                cryptoStream.Write(data, 0, data.Length);
                cryptoStream.FlushFinalBlock();
                cryptoStream.Close();
            }
            catch(Exception e)
            {
            	throw new DecryptionFailureException(e.Message);
            }
            finally
            {
            	if(EncryptionAlg != null)
            	{
            		EncryptionAlg.Clear();
            	}
            }
            
            return new PlainText(memoryStream.ToArray());
        }
	
	}
}
